﻿monster.board = (function () {
    //game functions go here
    var settings, monsters, cols, rows, baseScore, numMonsterTypes;

    function initialize(callback) {
        settings = monster.settings;
        numMonsterTypes = settings.numMonsterTypes;
        baseScore = settings.baseScore;
        cols = settings.cols;
        rows = settings.rows;
        fillBoard();
        callback();
    }

    function print() {
        var str = '';
        for (var y = 0; y < rows; y++) {
            for (var x = 0; x < cols; x++) {
                str += getMonster(x, y) + ' ';
            }
            str += '\r\n';
        }
        console.log(str);
    }

    function fillBoard() {
        var x, y, type;
        monsters = [];
        for (var x = 0; x < cols; x++) {
            monsters[x] = [];
            for (var y = 0; y < rows; y++) {
                type = randomMonster();
                while ((type === getMonster(x - 1, y) && type === getMonster(x - 2, y)) || (type === getMonster(x, y - 1) && type === getMonster(x, y - 2))) {
                    type = randomMonster();
                }
                monsters[x][y] = type;
            }
        }
        if (!hasMoves) {
            fillBoard();
        }
    }

    function getMonster(x, y) {
        if (x < 0 || x > cols - 1 || y < 0 || y > rows - 1) {
            return -1;
        } else {
            return monsters[x][y];
        }
    }

    function randomMonster() {
        return Math.floor(Math.random() * numMonsterTypes);
    }

    // returns the number of monsters in the longest chain that includes (x,y)
    function checkChain(x, y) {
        var type = getMonster(x, y),
            left = 0,
            right = 0,
            down = 0,
            up = 0;

        // look left
        while (type === getMonster(x - left - 1, y)) {
            left++;
        }

        // look right
        while (type === getMonster(x + right + 1, y)) {
            right++;
        }

        // look up
        while (type === getMonster(x, y + up + 1)) {
            up++;
        }

        // look down
        while (type === getMonster(x, y - down - 1)) {
            down++;
        }

        return Math.max(left + 1 + right, up + 1 + down);
    }

    // returns true if (x1,y1) can be swapped with (x2,y2) to form a new match
    function canSwap(x1, y1, x2, y2) {
        var type1 = getMonster(x1, y1),
            type2 = getMonster(x2, y2),
            chain;
        if (!isAdjacent(x1, y1, x2, y2)) {
            return false;
        }

        // temporarily swap monsters
        monsters[x1][y1] = type2;
        monsters[x2][y2] = type1;
        chain = (checkChain(x2, y2) > 2 || checkChain(x1, y1) > 2);

        // swap back
        monsters[x1][y1] = type1;
        monsters[x2][y2] = type2;

        return chain;
    }

    function isAdjacent(x1, y1, x2, y2) {
        var dx = Math.abs(x1 - x2),
            dy = Math.abs(y1 - y2);
        return (dx + dy === 1);
    }

    // returns a two-dimensional map of chain-lengths
    function getChains() {
        var x, y, chains = [];
        for (x = 0; x < cols; x++) {
            chains[x] = [];
            for (y = 0; y < rows; y++) {
                chains[x][y] = checkChain(x, y);
            }
        }

        return chains;
    }

    function check(events) {
        var chains = getChains(),
            hadChains = false,
            score = 0,
            removed = [],
            moved = [],
            gaps = [];
        for (var x = 0; x < cols; x++) {
            gaps[x] = 0;
            for (var y = rows - 1; y >= 0; y--) {
                if (chains[x][y] > 2) {
                    hadChains = true;
                    gaps[x]++;
                    removed.push({
                        x: x,
                        y: y,
                        type: getMonster(x,y)
                    });
                    // add points to score
                    score += baseScore * Math.pow(2, (chains[x][y] - 3));
                }
                else if (gaps[x] > 0) {
                    moved.push({
                        toX : x,
                        toY: y + gaps[x],
                        fromX: x,
                        fromY: y,
                        type: getMonster(x,y)
                    });
                }

                monsters[x][y + gaps[x]] = getMonster(x, y);
            }

            // fill from top
            for (y = 0; y < gaps[x]; y++) {
                monsters[x][y] = randomMonster();
                moved.push({
                    toX: x,
                    toY: y,
                    fromX: x,
                    fromY: y - gaps[x],
                    type: monsters[x][y]
                });
            }
        }

        events = events || [];
        if (hadChains) {
            events.push({
                type: 'remove',
                data: removed
            },
            {
                type: 'score',
                data: score
            },
            {
                type: 'move',
                data: moved
            });

            // refill if no more moves
            if (!hasMoves()) {
                fillBoard();
                events.push({
                    type : 'refill',
                    data : getBoard()
                });
            } 

            return check(events);
        }
        else {
            return events;
        }
    }

    // returns true if at least one match can be made
    function hasMoves() {
        for (x = 0; x < cols; x++) {
            for (y = 0; y < rows; y++) {
                if (canMonsterMove(x, y)) {
                    return true;
                }
            }
        }
        return false;
    }

    // returns true if (x,y) is a valid position and if the monster can be swapped with a neighbor
    function canMonsterMove(x, y) {
        return ((x > 0 && canSwap(x, y, x - 1, y)) || (y > 0 && canSwap(x, y, x, y - 1)) || (y < rows - 1 && canSwap(x, y, x, y + 1)));
    }

    // create a copy of the monster board
    function getBoard() {
        var copy = [],
            x;
        for (x = 0; x < cols; x++) {
            copy[x] = monsters[x].slice(0);
        }
        return copy;
    }

    // if possible, swaps (x1,y1) and (x2,y2) and calls the callback function with list of board events
    function swap(x1, y1, x2, y2, callback) {
        var tmp,
            events;
        if (canSwap(x1, y1, x2, y2)) {
            // swap the monsters
            tmp = getMonster(x1, y1);
            monsters[x1][y1] = getMonster(x2, y2);
            monsters[x2][y2] = tmp;
            // check the board and get the list of events
            events = check();
            callback(events);
        }
        else {
            callback(false);
        }
    }

    return {        
        initialize: initialize,        
        canSwap: canSwap,
        getBoard: getBoard,
        swap: swap,
        print: print
    };
})();